<template>
  <div class="cube-map">
    <h1>立方体贴图</h1>
    <p>
      立方体贴图（Cube Map）是一种特殊的纹理映射技术，它使用六个方向的纹理图像来创建环境映射效果。这种技术常用于创建天空盒、环境反射等效果。
    </p>

    <el-card class="demo-section">
      <template #header>
        <div class="card-header">
          <h3>立方体贴图示例</h3>
        </div>
      </template>
      <div class="demo-content">
        <canvas ref="canvas" width="400" height="400"></canvas>
        <div class="control-panel">
          <el-form :model="cubeMapForm" label-width="120px">
            <el-form-item label="纹理过滤方式">
              <el-select v-model="cubeMapForm.filterMode" @change="updateTextureParams">
                <el-option label="最近点采样" value="NEAREST" />
                <el-option label="线性过滤" value="LINEAR" />
              </el-select>
            </el-form-item>
            <el-form-item label="深度测试">
              <el-switch v-model="cubeMapForm.depthTest" @change="updateDepthTest" />
            </el-form-item>
          </el-form>
          <p>这是一个带有六个面不同贴图的立方体，你可以：</p>
          <ul>
            <li>修改纹理的过滤方式</li>
            <li>开启/关闭深度测试</li>
          </ul>
        </div>
      </div>
    </el-card>

    <el-card class="code-section">
      <template #header>
        <div class="card-header">
          <h3>示例代码</h3>
        </div>
      </template>
      <div class="code-content">
        <pre><code>
// 顶点着色器
const VSHADER_SOURCE = `
attribute vec4 a_Position;
varying vec3 v_Normal;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjMatrix;

void main() {
  gl_Position = u_ProjMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;
  // 使用顶点位置作为法线方向（需要归一化）
  v_Normal = normalize(a_Position.xyz);
}
`

// 片元着色器
const FSHADER_SOURCE = `
precision mediump float;
varying vec3 v_Normal;
uniform samplerCube u_Sampler;

void main() {
  // 使用归一化的法线方向采样立方体贴图
  gl_FragColor = textureCube(u_Sampler, normalize(v_Normal));
}
`
        </code></pre>
      </div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { WebGLUtils } from '@/utils/webgl'

// WebGL相关变量
const canvas = ref<HTMLCanvasElement | null>(null)
let gl: WebGLRenderingContext | null = null
let program: WebGLProgram | null = null

// 顶点着色器
const VSHADER_SOURCE = `
attribute vec4 a_Position;
varying vec3 v_Normal;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjMatrix;

void main() {
  gl_Position = u_ProjMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;
  // 使用顶点位置作为法线方向（需要归一化）
  v_Normal = normalize(a_Position.xyz);
}
`

// 片元着色器
const FSHADER_SOURCE = `
precision mediump float;
varying vec3 v_Normal;
uniform samplerCube u_Sampler;

void main() {
  // 使用归一化的法线方向采样立方体贴图
  gl_FragColor = textureCube(u_Sampler, normalize(v_Normal));
}
`

// 表单数据
const cubeMapForm = reactive({
  filterMode: 'LINEAR',
  depthTest: true
})

// 更新纹理参数
const updateTextureParams = () => {
  if (!gl) return
  const filter = gl[cubeMapForm.filterMode as keyof WebGLRenderingContext] as number
  const filterMode = cubeMapForm.filterMode === 'NEAREST' ? gl.NEAREST : gl.LINEAR_MIPMAP_LINEAR
  gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, filterMode)
  gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, filter)
}

// 更新深度测试
const updateDepthTest = () => {
  if (!gl) return
  if (cubeMapForm.depthTest) {
    gl.enable(gl.DEPTH_TEST)
  } else {
    gl.disable(gl.DEPTH_TEST)
  }
}

// 初始化顶点缓冲区
const initVertexBuffers = () => {
  if (!gl || !program) return

  const vertices = new Float32Array([
    // 只需要位置信息，不需要额外的纹理坐标
    // 正面
     1.0,  1.0,  1.0,
    -1.0,  1.0,  1.0,
    -1.0, -1.0,  1.0,
     1.0, -1.0,  1.0,
    // 背面
     1.0,  1.0, -1.0,
    -1.0,  1.0, -1.0,
    -1.0, -1.0, -1.0,
     1.0, -1.0, -1.0,
  ])

  const indices = new Uint8Array([
    0, 1, 2,   0, 2, 3,    // 前
    4, 0, 3,   4, 3, 7,    // 右
    5, 4, 7,   5, 7, 6,    // 后
    1, 5, 6,   1, 6, 2,    // 左
    4, 5, 1,   4, 1, 0,    // 上
    3, 2, 6,   3, 6, 7     // 下
  ])

  // 创建缓冲区对象
  const vertexBuffer = gl.createBuffer()
  const indexBuffer = gl.createBuffer()
  if (!vertexBuffer || !indexBuffer) {
    console.error('Failed to create buffer object')
    return
  }

  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW)

  const FSIZE = vertices.BYTES_PER_ELEMENT

  // 只需要设置位置属性
  const a_Position = gl.getAttribLocation(program, 'a_Position')
  gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 3, 0)
  gl.enableVertexAttribArray(a_Position)
}

// 初始化纹理
const initTextures = () => {
  if (!gl || !program) return

  // 创建纹理对象
  const texture = gl.createTexture()
  if (!texture) {
    console.error('Failed to create texture object')
    return
  }
  gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture)

  // 加载六个面的纹理图像
  const faces: Array<{ target: number, url: string }> = [
    { target: gl.TEXTURE_CUBE_MAP_POSITIVE_X, url: '/texture/cubemap/right.jpg' },
    { target: gl.TEXTURE_CUBE_MAP_NEGATIVE_X, url: '/texture/cubemap/left.jpg' },
    { target: gl.TEXTURE_CUBE_MAP_POSITIVE_Y, url: '/texture/cubemap/top.jpg' },
    { target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, url: '/texture/cubemap/bottom.jpg' },
    { target: gl.TEXTURE_CUBE_MAP_POSITIVE_Z, url: '/texture/cubemap/front.jpg' },
    { target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, url: '/texture/cubemap/back.jpg' }
  ]

  let loadedImages = 0
  faces.forEach(face => {
    const image = new Image()
    image.crossOrigin = 'anonymous'
    image.onload = () => {
      if (!gl) return
      gl.texImage2D(face.target, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)
      loadedImages++

      // 所有纹理加载完成后设置纹理参数
      if (loadedImages === faces.length) {
        gl.generateMipmap(gl.TEXTURE_CUBE_MAP);

        const filter = gl[cubeMapForm.filterMode as keyof WebGLRenderingContext] as number
        // 可以使用 MIPMAP
        const filterMode = cubeMapForm.filterMode === 'NEAREST' ? gl.NEAREST_MIPMAP_NEAREST : gl.LINEAR_MIPMAP_LINEAR
        gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, filterMode)
        gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, filter)
        gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
        gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)

        gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
        // 将纹理单元分配给着色器中的取样器变量
        gl.useProgram(program)
        const u_Sampler = gl.getUniformLocation(program!, 'u_Sampler')
        if (u_Sampler) {
          gl.uniform1i(u_Sampler, 0)
        }

        // 开始渲染
        draw()
      }
    }
    image.onerror = () => {
      console.error(`Failed to load texture: ${face.url}`)
    }
    image.src = face.url
  })
}

// 设置矩阵
let currentAngle = 0
const setMatrix = () => {
  if (!gl || !program) return

  // 更新旋转角度
  currentAngle = (currentAngle + 0.5) % 360

  // 模型矩阵 - 添加缩放和适当的旋转
  const scale = 0.5
  const modelMatrix = new Float32Array([
    scale * Math.cos(currentAngle * Math.PI / 180), 0, scale * -Math.sin(currentAngle * Math.PI / 180), 0,
    0, scale, 0, 0,
    scale * Math.sin(currentAngle * Math.PI / 180), 0, scale * Math.cos(currentAngle * Math.PI / 180), 0,
    0, 0, 0, 1
  ])

  // 视图矩阵 - 调整相机位置
  const viewMatrix = new Float32Array([
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, -5, 1
  ])

  // 投影矩阵
  const fov = 30 * Math.PI / 180
  const aspect = canvas.value!.width / canvas.value!.height
  const near = 1
  const far = 100
  const f = 1.0 / Math.tan(fov / 2)
  const projMatrix = new Float32Array([
    f / aspect, 0, 0, 0,
    0, f, 0, 0,
    0, 0, (far + near) / (near - far), -1,
    0, 0, (2 * far * near) / (near - far), 0
  ])

  // 将矩阵传递给着色器
  const u_ModelMatrix = gl.getUniformLocation(program, 'u_ModelMatrix')
  const u_ViewMatrix = gl.getUniformLocation(program, 'u_ViewMatrix')
  const u_ProjMatrix = gl.getUniformLocation(program, 'u_ProjMatrix')

  gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix)
  gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix)
  gl.uniformMatrix4fv(u_ProjMatrix, false, projMatrix)
}

// 绘制函数
const draw = () => {
  if (!gl) return

  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

  // 更新矩阵
  setMatrix()

  gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_BYTE, 0)
  requestAnimationFrame(draw)
}

// 初始化WebGL
const initWebGL = () => {
  if (!canvas.value) return
  gl = canvas.value.getContext('webgl')
  if (!gl) {
    console.error('Failed to get WebGL context')
    return
  }

  // 创建WebGL工具实例
  const webglUtils = new WebGLUtils(gl)

  // 创建着色器
  const vertexShader = webglUtils.createShader(gl.VERTEX_SHADER, VSHADER_SOURCE)
  const fragmentShader = webglUtils.createShader(gl.FRAGMENT_SHADER, FSHADER_SOURCE)
  if (!vertexShader || !fragmentShader) return

  // 创建程序并链接着色器
  program = webglUtils.createProgram(vertexShader, fragmentShader)
  if (!program) return

  gl.useProgram(program)

  // 设置顶点数据
  initVertexBuffers()

  // 开启深度测试
  gl.enable(gl.DEPTH_TEST)

  // 清除颜色和深度缓冲区
  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

  // 设置纹理 - 移到最后初始化
  initTextures()
}

// 组件挂载时初始化WebGL
onMounted(() => {
  initWebGL()
})
</script>

<style lang="scss" scoped>
.cube-map {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
  h1 {
    font-size: 2em;
    color: #333;
    margin-bottom: 1rem;
  }

  p {
    color: #666;
    line-height: 1.6;
    margin-bottom: 1.5rem;
  }

  .demo-section {
    margin: 20px 0;

    .demo-content {
      display: flex;
      gap: 30px;
      align-items: flex-start;

      canvas {
        border: 2px solid #e6e6e6;
        border-radius: 8px;
        box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
      }

      .control-panel {
        flex: 1;
        padding: 20px;
        background-color: #f8f9fa;
        border-radius: 8px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);

        .el-form {
          margin-bottom: 20px;
        }

        p {
          font-size: 14px;
          color: #606266;
          margin: 15px 0;
        }

        ul {
          list-style-type: none;
          padding-left: 0;
          margin: 10px 0;

          li {
            margin: 8px 0;
            color: #606266;
            font-size: 14px;
            position: relative;
            padding-left: 20px;

            &::before {
              content: "•";
              position: absolute;
              left: 0;
              color: #409eff;
            }
          }
        }
      }
    }
  }

  .code-section {
    margin: 20px 0;

    .code-content {
      background-color: #f8f9fa;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);

      pre {
        margin: 0;
        white-space: pre-wrap;
        font-family: "Monaco", "Menlo", "Consolas", monospace;

        code {
          color: #409eff;
        }
      }
    }
  }
}
</style>
